package cn.sportvip.service.sport;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import cn.sportvip.Generator;
import cn.sportvip.Sport;
import cn.sportvip.Sport.AGE;
import cn.sportvip.Sport.HIS;
import cn.sportvip.dto.sport.CoachDto;
import cn.sportvip.dto.sport.CourseDto;
import cn.sportvip.dto.sport.GymnasiumDto;
import cn.sportvip.entity.sport.Coach;
import cn.sportvip.entity.sport.CoachBasic;
import cn.sportvip.entity.sport.Course;
import cn.sportvip.entity.sport.CourseBasic;
import cn.sportvip.entity.sport.CourseHistory;
import cn.sportvip.entity.sport.Event;
import cn.sportvip.entity.sport.Gymnasium;
import cn.sportvip.entity.sport.GymnasiumBasic;
import cn.sportvip.entity.sport.ServicePhone;
import cn.sportvip.exception.GeneralException;
import cn.sportvip.repository.jpa.sport.CoachRepository;
import cn.sportvip.repository.jpa.sport.CourseHistoryRepository;
import cn.sportvip.repository.jpa.sport.CourseRepository;
import cn.sportvip.repository.jpa.sport.EventRepository;
import cn.sportvip.repository.jpa.sport.GymnasiumRepository;
import cn.sportvip.repository.jpa.sport.ServicePhoneRepository;
import cn.sportvip.repository.mongo.sport.CoachBasicRepository;
import cn.sportvip.repository.mongo.sport.CourseBasicRepository;
import cn.sportvip.repository.mongo.sport.GymnasiumBasicRepository;

import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
@Service
public class SportService {

	@Value("${static_file_path}")
	private String staticFilePath;
	
	@Value("${static_file_url}")
	private String staticFileUrlPrefix;
	
	@Autowired
	private GymnasiumRepository gymnasiumRepository;
	
	@Autowired
	private GymnasiumBasicRepository gymnasiumBasicRepository;
	
	@Autowired
	private CoachRepository coachRepository;
	
	@Autowired
	private CoachBasicRepository coachBasicRepository;
	
	@Autowired
	private ApplicationContext context;
	
	@Autowired
	private EventRepository eventRepository;
	
	@Autowired
	private CourseBasicRepository courseBasicRepository;
	
	@Autowired
	private CourseRepository courseRepository;
	
	@Autowired
	private CourseHistoryRepository courseHistoryRepository;
	
	@Autowired
	private ServicePhoneRepository servicePhoneRepository;
	
	@Autowired
	private MongoTemplate mongoTemplate;
	
	public String uploadFile(MultipartFile file) {
		String originalName = file.getOriginalFilename();
		String extension = null;
		String newFileName = Generator.genUuid();
		
		if(originalName.indexOf('.') > -1) {
			extension = originalName.substring(originalName.lastIndexOf('.'), originalName.length());
			newFileName += extension;
		}
		File newFile = new File(staticFilePath + newFileName);
		try {
			file.transferTo(newFile);
		} catch (IllegalStateException | IOException e) {
			throw new GeneralException("上传文件失败，请重试！", e);
		}

		return staticFileUrlPrefix + newFileName;
	}
	/**
	 * 查看场馆详情
	 * @param id
	 * @return
	 */
	@Cacheable(value = "permanent", key = "'gymnasium#' + #id")
	@Transactional(readOnly = true)
	public GymnasiumDto findGymnasiumById(Integer id) {
		Gymnasium gymnasium = gymnasiumRepository.findOne(id);
		GymnasiumBasic basic = gymnasiumBasicRepository.findOne(id);
		
		return buildGymnasiumDto(gymnasium, basic);
	}
	/**
	 * 查看附近场馆
	 * @param point
	 * @param pageable
	 * @param eventId 
	 * @param age 
	 * @param distance 
	 * @return
	 */
	@Transactional(readOnly = true)
	public Page<GymnasiumBasic> nearbyGymnasiums(Point point, Pageable pageable, int range, AGE age, int eventId) {
		// 有课
		BasicDBObject dbObject = new BasicDBObject("hasCourses", true);
		// 距离
		BasicDBList myLocation = new BasicDBList();
		myLocation.put(0, point.getY());
		myLocation.put(1, point.getX());
		BasicDBObject nearObject = null;
		if (range == 0) {
			nearObject = new BasicDBObject("$near", new BasicDBObject("$geometry",
							new BasicDBObject("type", "Point").append(
									"coordinates", myLocation)));
		} else {
			nearObject = new BasicDBObject("$near",
					new BasicDBObject("$geometry", new BasicDBObject("type",
							"Point").append("coordinates", myLocation)).append(
							"$maxDistance", range));
		}
		dbObject.append("location", nearObject);
		// 年龄段
		if(age != null) {
			dbObject.append("ageRange", age.toString());
		}
		// 项目
		if(eventId != 0){
			dbObject.append("events._id", eventId);
		}
		
		Query query = new BasicQuery(dbObject);
		List<GymnasiumBasic> basics = mongoTemplate.find(query.with(pageable), GymnasiumBasic.class);
		for(GymnasiumBasic basic : basics){
			double[] gyPoint = basic.getLocation();
			double distance = Sport.getDistance(point.getX(), point.getY(), gyPoint[1], gyPoint[0]);
			basic.setDistance(distance);
		}
		long total = mongoTemplate.count(query, GymnasiumBasic.class);
		return new PageImpl<>(basics, pageable, total);
	}
	
	/**
	 * 查看教练详情
	 * @param id
	 * @return
	 */
	@Cacheable(value = "permanent", key = "'coach#' + #id")
	@Transactional(readOnly = true)
	public CoachDto findCoachById(Integer coachId) {
		Coach coach = coachRepository.findOne(coachId);
		CoachBasic basic = coachBasicRepository.findOne(coachId);
		return buildCoachDto(coach, basic);
	}
	
	/**
	 * 附近教练
	 * @param gymnasium
	 * @param basic
	 * @return
	 */
	@Transactional(readOnly = true)
	public Page<CoachBasic> nearbyCoaches(Point point, Pageable pageable, int range, AGE age, int eventId) {
		// 有课
		BasicDBObject dbObject = new BasicDBObject("hasCourses", true);
		// 距离
		BasicDBList myLocation = new BasicDBList();
		myLocation.put(0, point.getY());
		myLocation.put(1, point.getX());
		BasicDBObject nearObject = null;
		if (range == 0) {
			nearObject = new BasicDBObject("$near", new BasicDBObject("$geometry",
							new BasicDBObject("type", "Point").append(
									"coordinates", myLocation)));
		} else {
			nearObject = new BasicDBObject("$near",
					new BasicDBObject("$geometry", new BasicDBObject("type",
							"Point").append("coordinates", myLocation)).append(
							"$maxDistance", range));
		}
		dbObject.append("location", nearObject);
		// 年龄段
		if(age != null) {
			dbObject.append("ageRange", age.toString());
		}
		// 项目
		if(eventId != 0){
			dbObject.append("events._id", eventId);
		}
		
		Query query = new BasicQuery(dbObject);
		List<CoachBasic> basics = mongoTemplate.find(query.with(pageable), CoachBasic.class);
		for(CoachBasic basic: basics) {
			double[] coachPoint = basic.getLocation();
			double distance = Sport.getDistance(point.getX(), point.getY(), coachPoint[1], coachPoint[0]);
			basic.setDistance(distance);
		}
		long total = mongoTemplate.count(query, CoachBasic.class);
		return new PageImpl<>(basics, pageable, total);
	}
	
	/***
	 * 查看系统中已经录入的所有运动项目名称
	 * @param gymnasium
	 * @param basic
	 * @return
	 */
	@Transactional(readOnly = true)
	@Cacheable(value = "permanent", key = "'allevents'")
	public Iterable<Event> findAllEvents() {
		return eventRepository.findAll();
	}
	
	/**
	 * 课程详情
	 * @param gymnasium
	 * @param basic
	 * @return
	 */
	@Transactional(readOnly = true)
	@Cacheable(value = "permanent", key = "'coursedto' + #id")
	public CourseDto findCourseDto(Integer id) {
		Course course = courseRepository.findOne(id);
		CourseBasic basic = courseBasicRepository.findOne(id);
		
		return buildCourseDto(course, basic);
	}
	
	/**
	 * 添加课程记录
	 * @param history
	 */
	@Transactional
	public CourseHistory addCourseHistory(CourseHistory history) {
		CourseHistory c = courseHistoryRepository.findByUserIdAndTargetIdAndType(history.getUserId(), history.getTargetId(), history.getType());
		if(c == null){
			return courseHistoryRepository.save(history);
		} else {
			return new CourseHistory();
		}
	}
	/***
	 * 查找我查看/联系过的课程
	 * @param gymnasium
	 * @param basic
	 * @return
	 */
	public Object findCourseHistory(Integer userId, HIS type, Point point,Pageable pageable) {
		Page<CourseHistory> histories = courseHistoryRepository.findByUserIdAndType(userId, type, pageable);
		if(type.equals(HIS.VIEW)) {
			List<CourseDto> dtos = new ArrayList<>(histories.getNumberOfElements());
			SportService proxy = context.getBean(this.getClass());
			for(CourseHistory h: histories) {
				dtos.add(proxy.findCourseDto(h.getTargetId()));
			}
			return new PageImpl<>(dtos, pageable, histories.getTotalElements());
		} else {
			List<CoachBasic> basics = new ArrayList<>(histories.getNumberOfElements());
			for(CourseHistory h : histories){
				basics.add(coachBasicRepository.findOne(h.getTargetId()));
			}
			for(CoachBasic basic: basics) {
				double[] coachPoint = basic.getLocation();
				double distance = Sport.getDistance(point.getX(), point.getY(), coachPoint[1], coachPoint[0]);
				basic.setDistance(distance);
			}
			return new PageImpl<>(basics, pageable, histories.getTotalElements());
		}
	}
	
	public Iterable<ServicePhone> findAllServicePhone() {
		return servicePhoneRepository.findAll();
	}
	// ------------private method------------------
	public GymnasiumDto buildGymnasiumDto(Gymnasium gymnasium, GymnasiumBasic basic) {
		GymnasiumDto dto = new GymnasiumDto();
		dto.address = basic.getAddress();
		dto.description = gymnasium.getDescription();
		dto.events = basic.getEvents();
		dto.id = gymnasium.getId();
		dto.name = basic.getName();
		dto.tags = basic.getTags();
		// 关联的教练
		SportService proxy = context.getBean(this.getClass());
		for(Coach c : gymnasium.getCoaches()) {
			dto.coaches.add(proxy.findCoachById(c.getId()));
		}
		// 关联的课程
		for(Course c : gymnasium.getCourses()) {
			dto.courses.add(buildCourseDto(c));
		}
		dto.pics = basic.getPics();
		dto.lat = basic.getLocation()[1];
		dto.lng = basic.getLocation()[0];
		return dto;
	}
	
	public CoachDto buildCoachDto(Coach coach, CoachBasic basic) {
		CoachDto dto = new CoachDto();
		dto.age = basic.getAge();
		dto.avatar = basic.getAvatar();
		dto.certificate = basic.getCertificate();
		dto.id = coach.getId();
		dto.introduction = coach.getIntroduction();
		dto.name = basic.getName();
		dto.phoneNum = basic.getPhones();
		dto.resume = coach.getResume();
		dto.tags = basic.getTags();
		dto.zan = basic.getZan();
		dto.honors = basic.getHonors();
		// 所教授的课程
		Set<CourseDto> dtos = new HashSet<>(coach.getCourses().size());
		for(Course c : coach.getCourses()) {
			dtos.add(buildCourseDto(c));
		}
		dto.courses = dtos;
		if(coach.getCourses().size() > 0){
			for(Course course : coach.getCourses()){
				Gymnasium gymnasium = course.getGymnasium();
				if(gymnasium != null){
					GymnasiumBasic gyBasic = gymnasiumBasicRepository.findOne(gymnasium.getId());
					dto.gymnasiumNames.add(gyBasic.getName());
					dto.address.add(gyBasic.getAddress());
					dto.locations.add(new Point(gyBasic.getLocation()[1], gyBasic.getLocation()[0]));
				}
			}
		}
		return dto;
	}
	
	public CourseDto buildCourseDto(Course c, CourseBasic basic) {
		CourseDto dto = new CourseDto();
		dto.schoolTime = c.getSchoolTime();
		dto.price = c.getPrice();
		dto.name = c.getName();
		Coach coach = c.getCoach();
		if(coach != null){
			dto.coachId = coach.getId();
			CoachBasic coachBasic = coachBasicRepository.findOne(coach.getId());
			dto.coachName = coachBasic.getName();
			dto.phoneNum = coachBasic.getPhones();
			dto.coachAvatar = coachBasic.getAvatar();
		}
		dto.advantage = c.getAdvantage();
		dto.age = basic.getAge();
		dto.description = c.getDescription();
		dto.address = basic.getAddress();
		dto.id = c.getId();
		dto.event = basic.getEvent();
		if(c.getGymnasium() != null) {
			dto.gymnasiumId = c.getGymnasium().getId();
			GymnasiumBasic gyBasic = gymnasiumBasicRepository.findOne(c.getGymnasium().getId());
			dto.gymnasiumName = gyBasic.getName();
			dto.location = new Point(gyBasic.getLocation()[1], gyBasic.getLocation()[0]);
		}
		return dto;
	}
	
	private CourseDto buildCourseDto(Course c) {
		CourseDto dto = new CourseDto();
		dto.schoolTime = c.getSchoolTime();
		dto.price = c.getPrice();
		dto.name = c.getName();
		Coach coach = c.getCoach();
		if(coach != null) {
			dto.coachId = coach.getId();
			CoachBasic basic = coachBasicRepository.findOne(dto.coachId);
			dto.coachName = basic.getName();
		}
		dto.id = c.getId();
		return dto;
	}
}
